﻿//=============================================================================
// Библиотека 'WPFExcelReport' предназначена для связи и передачи данных c PLC
//=============================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Text.RegularExpressions;
using WPFExcelReport;
using System.Threading;

namespace WPFExcelReport.S7
{
   
    /// <summary>
    /// Контроллер S7
    /// </summary>
    internal class S7 : IPlc
    {

        #region Константы



        #endregion

        #region Поля

        #region Общие структуры

        /// <summary>
        /// Список используемых DB для чтения
        /// </summary>
        private List<DB> _DBRead = new List<DB>(10);

        /// <summary>
        /// Список используемых DB для записи
        /// </summary>
        private List<DB> _DBWrite = new List<DB>(10);

        /// <summary>
        /// Область M для чтения
        /// </summary>
        private M _mRead = new M();

        /// <summary>
        /// Область M для записи
        /// </summary>
        private M _mWrite = new M();

        /// <summary>
        /// Значения переменных для чтения: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, ItemValue> _iReadRes = new Dictionary<string,ItemValue>(100);

        /// <summary>
        /// Значения переменных для записи: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, ItemValue> _iWriteRes = new Dictionary<string,ItemValue>(100);

        /// <summary>
        /// Адреса переменных для чтения: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, Addr> _iReadAddr = new Dictionary<string, Addr>(100);

        /// <summary>
        /// Адреса переменных для записи: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, Addr> _iWriteAddr = new Dictionary<string, Addr>(100);

        #endregion

        /// <summary>
        /// Описание PLC
        /// </summary>
        private PlcDesc _plc;

        /// <summary>
        /// Объект для разбора переменных
        /// </summary>
        private ParseItem _parseItem;

        /// <summary>
        /// Объект для передачи данных
        /// </summary>
        private TransferData _transData;

        /// <summary>
        /// Поток получения данных
        /// </summary>
        private Timer _threadRecData;

        /// <summary>
        /// Мьютекс для контроля потоков
        /// </summary>
        private Mutex _mtxThrTransData = new Mutex();
               
        /// <summary>
        /// Номер ошибки
        /// </summary>
        private int _errNum;

        /// <summary>
        /// Текст ошибки
        /// </summary>
        private string _errTxt;

        /// <summary>
        /// Адрес переменной с ошибкой
        /// </summary>
        private string _errAddr;

        #endregion

        #region Свойства

        /// <summary>
        /// Номер ошибки
        /// </summary>
        public int ErrNum
        {
            get
            {
                return _errNum;
            }
        }

        /// <summary>
        /// Описание ошибки
        /// </summary>
        public string ErrTxt
        {
            get
            {
                return _errTxt;
            }
        }

        /// <summary>
        /// Адрес переменной с ошибкой
        /// </summary>
        public string ErrAddr
        {
            get
            {
                return _errAddr;
            }
        }

        #endregion

        #region Конструктор

        /// <summary>
        /// Конструктор объекта PLCS7
        /// </summary>
        internal S7()
        {
            _parseItem = new ParseItem(_DBRead, _DBWrite, _mRead, _mWrite, _iReadRes, _iWriteRes, _iReadAddr, _iWriteAddr);
            _transData = new TransferData(_DBRead, _DBWrite, _mRead, _mWrite, _iReadRes, _iWriteRes, _iReadAddr, _iWriteAddr);
        }


        #endregion

        #region Внутренние методы

        /// <summary>
        /// Разбор ошибок
        /// </summary>
        /// <param name="err"> код ошибки</param>
        /// <param name="addr"> адрес переменной</param>
        private void ParseErrCode(ErrCode err, string addr = "")
        {
            switch (err)
            {
                case ErrCode.Ok:
                    _errNum = (int)ErrCode.Ok;
                    _errTxt = "ОК";
                    break;

                case ErrCode.AddrExist:
                    _errNum = (int)ErrCode.AddrExist;
                    _errAddr = addr;
                    _errTxt = "Адрес" + addr + " уже существует";
                    break;

                case ErrCode.AddrNoExist:
                    _errNum = (int)ErrCode.AddrNoExist;
                    _errAddr = addr;
                    _errTxt = "Адрес" + addr + " не существует";
                    break;

                case ErrCode.AddrFlt:
                    _errNum = (int)ErrCode.AddrFlt;
                    _errAddr = addr;
                    _errTxt = "Ошибка записи адреса " + addr;
                    break;

                case ErrCode.ConnFlt:
                    _errNum = (int)ErrCode.ConnFlt;
                    _errTxt = "Ошибка передачи данных. Проверьте адрес и данные PLC";
                    break;

                case ErrCode.DataReadFlt:
                    _errNum = (int)ErrCode.DataReadFlt;
                    _errTxt = "Ошибка доступа к данным для чтения. Проверьте существуют ли адреса в PLC";
                    break;

                case ErrCode.DataWriteFlt:
                    _errNum = (int)ErrCode.DataReadFlt;
                    _errTxt = "Ошибка доступа к данным для записи. Проверьте существуют ли адреса в PLC";
                    break;

                case ErrCode.PingNo:
                    _errNum = (int)ErrCode.PingNo;
                    _errTxt = "Ошибка связи, нет пинга. Проверьте сетевое подключение и адрес PLC";
                    break;
             }

        }

        /// <summary>
        /// Проверка переменной
        /// </summary>
        /// <param name="addr"> адрес переменной</param>
        /// <param name="valRw"> тип переменной </param>
        /// <param name="operType"> тип операции</param>
        /// <returns> ошибка</returns>
        private ErrCode CheckItem(string addr, ValRW valRw, OperationType operType)
        {
            if (operType == OperationType.Add)

                switch (valRw)
                {
                    case ValRW.Read:

                        // Проверка наличия адреса
                        if (_iReadAddr.ContainsKey(addr))
                        {
                            ParseErrCode(ErrCode.AddrExist, addr);
                            return ErrCode.AddrExist;
                        }

                        // Проверка записи адреса
                        if (!IsAddrValid(addr))
                        {
                            ParseErrCode(ErrCode.AddrFlt, addr);
                            return ErrCode.AddrFlt;
                        }

                        break;


                    case ValRW.Write:

                        // Проверяем наличие адреса
                        if (_iWriteAddr.ContainsKey(addr))
                        {
                            ParseErrCode(ErrCode.AddrExist, addr);
                            return ErrCode.AddrExist;
                        }

                        // Проверка записи адреса
                        if (!IsAddrValid(addr))
                        {
                            ParseErrCode(ErrCode.AddrFlt, addr);
                            return ErrCode.AddrFlt;
                        }
                        break;
                }


            if (operType == OperationType.Del)

                switch (valRw)
                {
                    case ValRW.Read:

                        // Проверка наличия адреса
                        if (!_iReadAddr.ContainsKey(addr))
                        {
                            ParseErrCode(ErrCode.AddrNoExist, addr);
                            return ErrCode.AddrNoExist;
                        }
                        break;


                    case ValRW.Write:

                        // Проверка наличия адреса
                        if (!_iWriteAddr.ContainsKey(addr))
                        {
                            ParseErrCode(ErrCode.AddrNoExist, addr);
                            return ErrCode.AddrNoExist;
                        }
                        break;
                }

            return ErrCode.Ok;
        }
                
        /// <summary>
        /// Передача данных
        /// </summary>
        /// <param name="o"></param>
        private void TransData(object o)
        {
            // блокируем поток
            _mtxThrTransData.WaitOne();
            
            // открываем соед, если закрыто               
            if (!_transData.IsConnect) _transData.Open();

            _transData.ItemsChange.Clear();

            _transData.ReadDataDB();
            _transData.ReadDataM();
            _transData.WriteDataDB();
            _transData.WriteDataM();
            
            // возбуждаем событие, если есть новые данные
            if ((DataChange != null) && (_transData.ItemsChange.Count > 0))
            {
                DataChange(null, new ReadDataEventArgs(_transData.ItemsChange));
            } 
                    
            // ошибки
            ParseErrCode(_transData.Error);
            if (_transData.IsConnect) _transData.Error = ErrCode.Ok;

            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();

        }
        

        #endregion

        #region Внешние методы

        /// <summary>
        /// Добавить переменные для чтения
        /// </summary>
        /// <param name="itemsRead"> переменные для чтения</param>
        /// <param name="error"> ошибка</param>
        /// <returns> указатели на переменные для чтения </returns>
        public List<HandlItemRead> AddItemsRead(List<ItemRead> itemsRead, out bool error)
        {
            // Проверяем элементы
            foreach (ItemRead item in itemsRead)
            {
                ErrCode err = CheckItem(item.Addr, ValRW.Read, OperationType.Add);

                if (err != ErrCode.Ok)
                {
                    error = true;
                    return null;
                }
            }

            // блокируем поток
            _mtxThrTransData.WaitOne();

            // Разбираем элементы
            List<HandlItemRead> res = new List<HandlItemRead>(itemsRead.Count);

            foreach (ItemRead item in itemsRead)
            {
                _parseItem.AddItemRead(item);
                res.Add(new HandlItemRead(item.Name, item.Addr));
            }

            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();

            error = false;
            return res;

        }

        /// <summary>
        /// Удалить переменные для чтения
        /// </summary>
        /// <param name="itemsRead"> указатели на переменные для чтения</param>
        /// <param name="error"> ошибка</param>
        public void DelItemsRead(List<HandlItemRead> itemsRead, out bool error)
        {
            // Проверяем элементы
            foreach (HandlItemRead item in itemsRead)
            {
                ErrCode err = CheckItem(item.Addr, ValRW.Read, OperationType.Del);

                if (err != ErrCode.Ok)
                {
                    error = true;
                    return;
                }
            }

            // блокируем поток
            _mtxThrTransData.WaitOne();

            // Удаляем элементы
            foreach (HandlItemRead item in itemsRead)
            {
                _parseItem.DelItemRead(item);
            }
            error = false;

            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();
        }

        /// <summary>
        /// Чтение переменных
        /// </summary>
        /// <param name="itemsRead"> указатели на переменные для чтения</param>
        /// <param name="error"> ошибка</param>
        /// <returns> результаты чтения </returns>
        public List<ItemValue> ReadData(List<HandlItemRead> itemsRead, out bool error)
        {
            List<ItemValue> res = new List<ItemValue>(itemsRead.Count);

            foreach (HandlItemRead item in itemsRead)
            {
                try
                {
                    res.Add(_iReadRes[item.Addr]);
                }
                catch (KeyNotFoundException ex)
                {
                    error = true;
                    ParseErrCode(ErrCode.AddrNoExist, item.Addr);
                    return null;
                }
            }

            error = false;
            return res;
        }

        /// <summary>
        /// Добавить переменные для записи
        /// </summary>
        /// <param name="itemsWrite"> переменные для записи</param>
        /// <param name="error"> ошибка</param>
        /// <returns> указатели на переменные для записи </returns>
        public List<HandlItemWrite> AddItemsWrite(List<ItemWrite> itemsWrite, out bool error)
        {
            // Проверяем элементы
            foreach (ItemWrite item in itemsWrite)
            {
                ErrCode err = CheckItem(item.Addr, ValRW.Write, OperationType.Add);

                if (err != ErrCode.Ok)
                {
                    error = true;
                    return null;
                }
            }

            // блокируем поток
            _mtxThrTransData.WaitOne();

            // Разбираем элементы
            List<HandlItemWrite> res = new List<HandlItemWrite>(itemsWrite.Count);

            foreach (ItemWrite item in itemsWrite)
            {
                _parseItem.AddItemWrite(item);
                res.Add(new HandlItemWrite(item.Name, item.Addr, new object()));
            }

            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();

            error = false;
            return res;
        }

        /// <summary>
        /// Удалить переменные для записи
        /// </summary>
        /// <param name="itemsWrite"> указатели на переменные для записи</param>
        /// <param name="error"> ошибка</param>
        public void DelItemsWrite(List<HandlItemWrite> itemsWrite, out bool error)
        {
            // Проверяем элементы
            foreach (HandlItemWrite item in itemsWrite)
            {
                ErrCode err = CheckItem(item.Addr, ValRW.Write, OperationType.Del);

                if (err != ErrCode.Ok)
                {
                    error = true;
                    return;
                }
            }

            // блокируем поток
            _mtxThrTransData.WaitOne();

            // Удаляем элементы
            foreach (HandlItemWrite item in itemsWrite)
            {
                _parseItem.DelItemWrite(item);
            }
            error = false;

            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();
        }

        /// <summary>
        /// Запись переменных
        /// </summary>
        /// <param name="itemsWrite"> указатели на переменные для записи</param>
        /// <param name="error"> ошибка</param>
        public void WriteData(List<HandlItemWrite> itemsWrite, out bool error)
        {
            // Проверяем элементы
            foreach (HandlItemWrite item in itemsWrite)
            {
                if (!_iWriteRes.ContainsKey(item.Addr))
                {
                    error = true;
                    ParseErrCode(ErrCode.AddrNoExist, item.Addr);
                    return;
                }
            }

            // блокируем поток
            _mtxThrTransData.WaitOne();
           
            // Подготовка для записи, - запись будет в след передаче данных
            if (_transData.IsConnect)
                foreach (HandlItemWrite item in itemsWrite)
                     _parseItem.UpdItemWrite(item);
                
            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();

            error = false;
            return;

        }

        /// <summary>
        /// Проверка правильности записи адреса
        /// </summary>
        /// <param name="addr"> адрес переменной</param>
        /// <returns> true - ок</returns>
        public bool IsAddrValid(string addr)
        {
            string s = addr.Trim().ToLower();

            string dbByteToRealPatt = @"^db\d+\.(dbb|dbw|dbd|real)\d+$";
            string dbStrPatt = @"^db\d+\.(string)\d+\.\d+$";

            string mbyteToRealPatt = @"^(mw|mb|md|mr)\d+$";
            string mstringPatt = @"^mstr\d+\.\d+$";

            bool res = Regex.IsMatch(s, dbByteToRealPatt) || Regex.IsMatch(s, dbStrPatt)
                || Regex.IsMatch(s, mbyteToRealPatt) || Regex.IsMatch(s, mstringPatt);

            if (!res) return false;

            return true;

        }

        /// <summary>
        /// Очистка переменных для чтения
        /// </summary>
        public void ClearItemsRead()
        {
            // блокируем поток
            _mtxThrTransData.WaitOne();

            _DBRead.Clear();
            _mRead.Handls.Clear();
           
            _iReadAddr.Clear();
            _iReadRes.Clear();
            
            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();
        }

        /// <summary>
        /// Очистка переменных для записи
        /// </summary>
        public void ClearItemsWrite()
        {
            // блокируем поток
            _mtxThrTransData.WaitOne();

            _DBWrite.Clear();
            _mWrite.Handls.Clear();

            _iWriteAddr.Clear();
            _iWriteRes.Clear();

            // разблокируем поток
            _mtxThrTransData.ReleaseMutex();
        }

        /// <summary>
        /// Соединение с PLC
        /// </summary>
        /// <param name="plc"> описание plc</param>
        /// <returns> код ошибки</returns>
        public int Connect(PlcDesc plc)
        {
            if (_transData.IsConnect) return (int)ErrCode.Ok;

            _plc = plc;
          
            // Иниц-я параметров связи
            ErrCode err = _transData.Init(plc);
            if (err != ErrCode.Ok)
            {
                ParseErrCode(err);
                return (int)err;
            }

            // Пинг
            if (!_transData.Ping())
            {
                ParseErrCode(ErrCode.PingNo);
                return (int)ErrCode.PingNo;
            }
                     
            // Открываем соединение
            _transData.Open();
           
            if (_transData.Error != ErrCode.Ok)
            {
                ParseErrCode(_transData.Error);
                return (int)_transData.Error;
            }

            // запуск потока передачи данных
            if (_threadRecData != null) _threadRecData.Dispose();
            _threadRecData = new Timer(TransData, null, 10, _plc.TimeUpd);
                      
            return (int)ErrCode.Ok;
        }

        /// <summary>
        /// Разъединение с PLC
        /// </summary>
        public void Disconnect()
        {
            if (_transData.IsConnect)
            {
                _transData.Close();
                _threadRecData.Dispose();
            }
        }

        /// <summary>
        /// Связь
        /// </summary>
        /// <returns> true - ok</returns>
        public bool GetConnState()
        {
            return _transData.IsConnect;
        }

        #endregion

        #region События

        /// <summary>
        /// Изменение данных для чтения
        /// </summary>
        public event EventHandler<ReadDataEventArgs> DataChange;

        #endregion



    }




}

